package com.icehand.payment.pay.wechat;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.google.common.base.Strings;
import com.icehand.common.code.PaymentCode;
import com.icehand.common.constants.CommonConstants;
import com.icehand.common.enums.PayChannelEnum;
import com.icehand.common.enums.SignTypeEnum;
import com.icehand.common.enums.TradeStatusEnum;
import com.icehand.common.model.BaseOrder;
import com.icehand.mybatis.payment.domain.PayNotifyRecord;
import com.icehand.mybatis.payment.mapper.PayNotifyRecordMapper;
import com.icehand.payment.model.NotifyWrapper;
import com.icehand.payment.model.PayWrapper;
import com.icehand.payment.pay.Pay;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.springframework.web.client.RestTemplate;

import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.Date;
import java.util.Map;

import static com.icehand.payment.pay.wechat.WeChatPaymentSignatureUtils.generateNonceStr;

/**
 * 微信基础支付类
 * @author icehand
 */
@Slf4j
public abstract class BaseWechatPay implements Pay {
    private static final String UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";
    protected final RestTemplate restTemplate;
    protected final XmlMapper xmlMapper;
    protected final PayNotifyRecordMapper payNotifyRecordMapper;

    protected BaseWechatPay(RestTemplate restTemplate, XmlMapper xmlMapper,
                            PayNotifyRecordMapper payNotifyRecordMapper) {
        this.restTemplate = restTemplate;
        this.xmlMapper = xmlMapper;
        this.payNotifyRecordMapper = payNotifyRecordMapper;
    }

    @Override
    public void pay(PayWrapper payWrapper) {
        if(!checkPayWrapper(payWrapper)){
            payWrapper = new PayWrapper();
            payWrapper.error(PaymentCode.PAY_PARAM_ERROR);
            return;
        }

        if(!isWechatPay(payWrapper.getPayChannelEnum())){
            return;
        }

        doAfter(payWrapper, doUnifiedOrder(payWrapper));
    }

    @Override
    public void payNotify(NotifyWrapper notifyWrapper){
        Map<String,String> map;
        try {
            String notifyStr = getWechatNotifyStr(notifyWrapper.getRequest());
            map = xmlMapper.readValue(notifyStr,new TypeReference<Map<String,String>>(){});
        } catch (IOException e) {
            log.error("parse wechat notify data error: {}",e);
            notifyWrapper.getBizResult().error(PaymentCode.PAY_NOTIFY_PARAM_ERROR);
            return;
        }

        //校验签名
        if(!WeChatPaymentSignatureUtils.isSignatureValid(map,
                notifyWrapper.getConfig().getString(CommonConstants.K_SECRET))){
            log.warn("check sign fail, response map -> {}",map);
            return;
        }

        JSONObject jsonObject = new JSONObject();
        jsonObject.putAll(map);
        WeChatNotify weChatNotify = jsonObject.toJavaObject(WeChatNotify.class);

        BaseOrder baseOrder = new BaseOrder()
                .setOrderNo(weChatNotify.getOutTradeNo())
                .setAmount(new BigDecimal(weChatNotify.getTotalFee()).divide(new BigDecimal("100"),0,BigDecimal.ROUND_UP))
                .setPayTime(new Date())
                .setTradeStatus(TradeStatusEnum.SUCCESS);

        if(null != payNotifyRecordMapper){
            PayNotifyRecord record = new PayNotifyRecord()
                    .setAppId(notifyWrapper.getAppId())
                    .setChannelId(notifyWrapper.getPayChannelEnum().getChannelId())
                    .setAmount(baseOrder.getAmount())
                    .setOrderNo(baseOrder.getOrderNo())
                    .setTradeNo(weChatNotify.getTransactionId())
                    .setTradeStatus(baseOrder.getTradeStatus().getStatus())
                    .setCreateTime(new Date())
                    .setNotifyData(JSONObject.toJSONString(weChatNotify));
            payNotifyRecordMapper.insert(record);
        }

        notifyWrapper.getBizResult().putData(baseOrder);
    }

    protected String getWechatNotifyStr(HttpServletRequest request) throws IOException {
        ServletInputStream in = request.getInputStream();
        int size = request.getContentLength();
        byte[] data = new byte[size];
        int num = in.read(data);
        if(num < 0){
            return null;
        }
        return new String(data, StandardCharsets.UTF_8);
    }

    protected boolean checkPayWrapper(PayWrapper payWrapper){
        return null != payWrapper
                && null != payWrapper.getPayChannelEnum()
                && null != payWrapper.getConfig()
                &&  null != payWrapper.getBaseOrder()
                && 0 != payWrapper.getConfig().size();
    }

    protected abstract boolean isWechatPay(PayChannelEnum payChannelEnum);

    protected WeChatPaymentUnifiedOrderRequest createUnifiedOrder(PayWrapper payWrapper){
        return new WeChatPaymentUnifiedOrderRequest()
                .setAppid(payWrapper.getConfig().getString("app_id"))
                .setMch_id(payWrapper.getConfig().getString("mch_id"))
                .setNonce_str(generateNonceStr())
                .setOut_trade_no(payWrapper.getBaseOrder().getOrderNo())
                .setSign_type(SignTypeEnum.MD5.getType())
                .setTime_start(new DateTime(payWrapper.getBaseOrder().getCreateTime()).toString("yyyyMMddHHmmss"))
                .setTime_expire(new DateTime(payWrapper.getBaseOrder().getExpireTime()).toString("yyyyMMddHHmmss"))
                .setTotal_fee(payWrapper.getBaseOrder().getAmount().multiply(new BigDecimal("100"))
                        .setScale(0,BigDecimal.ROUND_UP).toPlainString())
                .setSpbill_create_ip(payWrapper.getExtendMap().get("clientIp"))
                .setBody(payWrapper.getBaseOrder().getBody())
                .setNotify_url(payWrapper.getConfig().getString("notifyUrl"));
    }

    /**
     * 后续签名操作
     * @param payWrapper payWrapper
     * @param response response
     */
    protected abstract void doAfter(PayWrapper payWrapper, WeChatPaymentUnifiedOrderResponse response);

    /**
     * 统一下单
     */
    private WeChatPaymentUnifiedOrderResponse doUnifiedOrder(PayWrapper payWrapper) {
        WeChatPaymentUnifiedOrderRequest request = createUnifiedOrder(payWrapper);
        if(!payWrapper.getBizResult().isSuccess()){
            return null;
        }

        request.createSign(payWrapper.getConfig().getString(CommonConstants.K_SECRET));
        XmlMapper xmlMapper = new XmlMapper();
        String requestStr;
        try {
            requestStr = xmlMapper.writeValueAsString(request);
        } catch (JsonProcessingException e) {
            e.printStackTrace();
            payWrapper.error(PaymentCode.PAY_FAIL);
            return null;
        }

        String responseStr = restTemplate.postForObject(UNIFIED_ORDER_URL,requestStr, String.class);
        if(Strings.isNullOrEmpty(responseStr)){
            log.warn("request fail, return null");
            payWrapper.error(PaymentCode.PAY_FAIL);
            return null;
        }

        Map<String,String> map;
        try {
            map = xmlMapper.readValue(responseStr,new TypeReference<Map<String,String>>(){});
        } catch (IOException e) {
            e.printStackTrace();
            payWrapper.error(PaymentCode.PAY_FAIL);
            return null;
        }

        if(null == map){
            log.warn("xml 2 map fail , requestStr , resultStr:{}",requestStr, responseStr);
            payWrapper.error(PaymentCode.PAY_FAIL);
            return null;
        }

        boolean checkSign = WeChatPaymentSignatureUtils.isSignatureValid(map,
                payWrapper.getConfig().getString(CommonConstants.K_SECRET), SignTypeEnum.MD5);
        if(!checkSign){
            log.warn("check sign fail, request str -> {} , response str -> {}",requestStr, responseStr);
            payWrapper.error(PaymentCode.PAY_FAIL);
            return null;
        }

        JSONObject jsonObject = new JSONObject();
        jsonObject.putAll(map);
        return jsonObject.toJavaObject(WeChatPaymentUnifiedOrderResponse.class);
    }
}
